package cn.com.doone.common.uc.oauth.authorize;

import static cn.com.doone.common.uc.domain.oauth.Constants.EDIT_PASSWORD;
import static cn.com.doone.common.uc.domain.oauth.Constants.OAUTH_APPROVAL_VIEW;
import static cn.com.doone.common.uc.domain.oauth.Constants.OAUTH_LOGIN_VIEW;
import static cn.com.doone.common.uc.domain.oauth.Constants.REQUEST_PASSWORD;
import static cn.com.doone.common.uc.domain.oauth.Constants.REQUEST_USERNAME;
import static cn.com.doone.common.uc.domain.oauth.Constants.REQUEST_USER_OAUTH_APPROVAL;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;

import cn.com.doone.common.uc.domain.oauth.AccessToken;
import cn.com.doone.common.uc.oauth.OAuthAuthxRequest;
import cn.com.doone.common.uc.oauth.OAuthHandler;
import cn.com.doone.common.uc.oauth.validator.AbstractClientDetailsValidator;
import cn.com.doone.common.uc.service.dto.LoginLog;
import cn.com.doone.common.uc.utils.HttpClientUtil;
import cn.com.doone.common.uc.utils.PasswordUtils;
import cn.com.doone.common.uc.utils.RedisUtils;
import cn.com.doone.common.uc.utils.StringUtils;
import cn.com.doone.common.uc.web.WebUtils;

/**
 * 2015/6/25
 *
 * @author Shengzhao Li
 */
public abstract class AbstractAuthorizeHandler extends OAuthHandler {

	private static final Logger LOG = LoggerFactory.getLogger(AbstractAuthorizeHandler.class);

	protected OAuthAuthxRequest oauthRequest;
	protected HttpServletResponse response;
	protected HttpServletRequest request;

	protected boolean userFirstLogged = false;
	protected boolean userFirstApproved = false;

	private int userId;
	private int appId;
	private Map<String, Object> appInfo;
	private Map<String, Object> userInfo;

	public AbstractAuthorizeHandler(OAuthAuthxRequest oauthRequest, HttpServletResponse response) {
		this.oauthRequest = oauthRequest;
		this.response = response;
	}

	public AbstractAuthorizeHandler(OAuthAuthxRequest oauthRequest, HttpServletResponse response,
			HttpServletRequest request) {
		this.oauthRequest = oauthRequest;
		this.response = response;
		this.request = request;
	}

	protected boolean validateFailed() throws OAuthSystemException {
		AbstractClientDetailsValidator validator = getValidator();
		LOG.debug("Use [{}] validate client: {}", validator, oauthRequest.getClientId());

		final OAuthResponse oAuthResponse = validator.validate();
		return checkAndResponseValidateFailed(oAuthResponse);
	}

	protected abstract AbstractClientDetailsValidator getValidator();

	protected boolean checkAndResponseValidateFailed(OAuthResponse oAuthResponse) {
		if (oAuthResponse != null) {
			LOG.debug("Validate OAuthAuthzRequest(client_id={}) failed", oauthRequest.getClientId());
			// WebUtils.writeOAuthJsonResponse(response, oAuthResponse);
			try {
				org.apache.shiro.web.util.WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
			} catch (IOException e) {
				e.printStackTrace();
				WebUtils.writeOAuthJsonResponse(response, oAuthResponse);
			}
			return true;
		}
		return false;
	}

	protected String clientId() {
		return oauthRequest.getClientId();
	}

	protected boolean isUserAuthenticated() {
		final Subject subject = SecurityUtils.getSubject();
		return subject.isAuthenticated();
	}

	protected boolean isNeedUserLogin() {
		return !isUserAuthenticated() && !isPost();
	}

	protected boolean goApproval() throws ServletException, IOException {
		// 验证用户是否已授权访问应用
		if (this.oauthService.valiUserAndAppAuthorize(this.getUserId(), this.getAppId())) { // 已授权
			userFirstLogged = false;
		}
		if (userFirstLogged && !clientDetails().trusted()) {
			// go to approval
			LOG.debug("Go to oauth_approval, clientId: '{}'", clientDetails().getClientId());
			final HttpServletRequest request = oauthRequest.request();
			// 设置授权页的回调链接
			request.setAttribute("redirect_uri", request.getParameter("redirect_uri"));
			request.setAttribute("appIconUri", clientDetails().getIconUri());
			request.getRequestDispatcher(OAUTH_APPROVAL_VIEW).forward(request, response);
			return true;
		}
		return false;
	}

	// true is submit failed, otherwise return false
	protected boolean submitApproval() throws IOException, OAuthSystemException {
		if (isPost() && !clientDetails().trusted()) {
			// submit approval
			final HttpServletRequest request = oauthRequest.request();
			final String oauthApproval = request.getParameter(REQUEST_USER_OAUTH_APPROVAL);

			if (this.oauthService.valiUserAndAppAuthorize(this.getUserId(), this.getAppId())) {
				return false;
			}

			if (!"true".equalsIgnoreCase(oauthApproval)) {
				// Deny action
				LOG.debug("User '{}' deny access", SecurityUtils.getSubject().getPrincipal());
				responseApprovalDeny();
				return true;
			} else {
				// 记录用户授权应用数据
				this.oauthService.saveUserAppAuthorize(this.getUserId(), this.getAppId());

				userFirstApproved = true;
				return false;
			}
		}
		return false;
	}

	protected void responseApprovalDeny() throws IOException, OAuthSystemException {

		String locationUri = HttpClientUtil
				.decodeURIComponent(HttpClientUtil.decodeURIComponent(oauthRequest.getRedirectURI()));

		final OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
				.setError(OAuthError.CodeResponse.ACCESS_DENIED).setErrorDescription("User denied access")
				// .location(clientDetails().getRedirectUri())
				.location(locationUri).setState(oauthRequest.getState()).buildQueryMessage();
		LOG.debug("'ACCESS_DENIED' response: {}", oAuthResponse);

		WebUtils.writeOAuthQueryResponse(response, oAuthResponse);
		/*
		 * try { org.apache.shiro.web.util.WebUtils.toHttp(response).sendError(
		 * HttpServletResponse.SC_UNAUTHORIZED); } catch (IOException e) {
		 * WebUtils.writeOAuthQueryResponse(response, oAuthResponse);
		 * e.printStackTrace(); }
		 */

		// user logout when deny
		final Subject subject = SecurityUtils.getSubject();
		subject.logout();
		LOG.debug("After 'ACCESS_DENIED' call logout. user: {}", subject.getPrincipal());
	}

	protected boolean goLogin() throws ServletException, IOException {
		if (isNeedUserLogin()) {
			// go to login
			LOG.debug("Forward to Oauth login by client_id '{}'", oauthRequest.getClientId());
			final HttpServletRequest request = oauthRequest.request();
			String clientId = oauthRequest.getClientId();
			request.setAttribute("clientId", clientId);
			request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);
			return true;
		}
		return false;
	}

	// true is login failed, false is successful
	protected boolean submitLogin() throws ServletException, IOException {
		if (isSubmitLogin()) {
			// login flow
			try {
				// 判空
				String message = valiUserNamePassword();
				String username = oauthRequest.request().getParameter(REQUEST_USERNAME);
				final HttpServletRequest request = oauthRequest.request();
				if (!message.isEmpty()) {
					//是否要判断账号锁
					if(propertiesUtils.isCheckLock()) {
						//写redis数据
						RedisUtils.addAccountList(username);
					}
					
					request.setAttribute("username", request.getParameter(REQUEST_USERNAME));
					request.setAttribute("oauth_login_error", true);
					request.setAttribute("oauth_login_error_message", message);
					request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);
					return true;
				}

				UsernamePasswordToken token = createUsernamePasswordToken();
				SecurityUtils.getSubject().login(token);

				// 保存登录日志
				this.logLogin(request.getParameter(REQUEST_USERNAME), "1");
				
				if(propertiesUtils.isCheckLock()) {
					//清除redis数据
					RedisUtils.delAccountList(username);;
				}

				// 更新账号锁定状态
				Map<String, Object> userMap = new HashMap<String, Object>();
				int userInfoId = this.oauthService.findUserInfoIdByAccount(request.getParameter(REQUEST_USERNAME));
				userMap.put("userInfoId", userInfoId);
				userMap.put("lockLevel", "0");
				userMap.put("invalidTime", null);

				this.oauthService.updateUserLock(userMap);

				LOG.debug("Submit login successful");
				this.userFirstLogged = true;
				return false;
			} catch (Exception ex) {
				LOG.debug("Login failed, back to login page too", ex);
				final HttpServletRequest request = oauthRequest.request();
				
				//写redis数据
				RedisUtils.addAccountList(request.getParameter(REQUEST_USERNAME));
				RedisUtils.addIpList(request);
				
				// 保存登录日志
				this.logLogin(request.getParameter(REQUEST_USERNAME), "0");

				request.setAttribute("username", request.getParameter(REQUEST_USERNAME));
				request.setAttribute("oauth_login_error", true);
				request.setAttribute("oauth_login_error_message", "用户名或密码错误！！");
				request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);

				String loginType = StringUtils.isNull(oauthRequest.getParam("appCode")) ? "WEB"
						: oauthRequest.getParam("appCode");
				Map<String, Object> loggerMap = new HashMap<String, Object>();
				loggerMap.put("login_account", request.getParameter(REQUEST_USERNAME));
				loggerMap.put("login_type", loginType);
				loggerMap.put("login_app", clientId());
				loggerMap.put("is_success", false);
				loggerMap.put("reason", 1);
				LOG.info(loggerMap.toString());

				return true;
			}
		}
		return false;
	}

	/**
	 * 验证是否为弱密码
	 * 
	 * @return
	 * @throws Exception
	 */
	protected boolean isWeakPassword() throws Exception {
		try {
			// todo
			final HttpServletRequest request = oauthRequest.request();
			String password = request.getParameter(REQUEST_PASSWORD);

			if (password != null && !password.isEmpty()) {
				String checkMsg = PasswordUtils.checkPassword(password);
				if (StringUtils.isNotNull(checkMsg)) {
					request.setAttribute("username", request.getParameter(REQUEST_USERNAME));
					request.setAttribute("error_message", "您当前使用的密码为弱密码，请修改密码！");
					request.getRequestDispatcher(EDIT_PASSWORD).forward(request, response);
					// 如果为弱密码，登出
					final Subject subject = SecurityUtils.getSubject();
					subject.logout();
					return true;
				}
			}

		} catch (Exception ex) {
			LOG.debug("vali weak password error", ex);
			final Subject subject = SecurityUtils.getSubject();
			subject.logout();
			throw ex;
		}

		return false;
	}

	private String valiUserNamePassword() {
		String message = "";

		final HttpServletRequest request = oauthRequest.request();
		final String username = request.getParameter(REQUEST_USERNAME);
		final String password = request.getParameter(REQUEST_PASSWORD);

		if (username.trim().isEmpty()) {
			message = "请输入账号";
		} else if (password.trim().isEmpty()) {
			message = "请输入密码";
		}

		return message;
	}

	private UsernamePasswordToken createUsernamePasswordToken() {
		final HttpServletRequest request = oauthRequest.request();
		final String username = request.getParameter(REQUEST_USERNAME);
		final String password = request.getParameter(REQUEST_PASSWORD);
		return new UsernamePasswordToken(username, password);
	}

	protected boolean isSubmitLogin() {
		return !isUserAuthenticated() && isPost();
	}

	protected boolean isPost() {
		return RequestMethod.POST.name().equalsIgnoreCase(oauthRequest.request().getMethod());
	}

	/**
	 * 验证用户是否有权限访问应用
	 * 
	 * @author whm
	 * @return
	 * @throws ServletException
	 * @throws IOException
	 */
	protected boolean valiAuthorize() throws ServletException, IOException {
		try {

			if ("1".equals((String) appInfo.get("APP_TYPE")) && "1".equals((String) appInfo.get("STATUS"))) {
				return false;
			}

			// 验证用户所属的所有用户组是否有权访问应用
			if (!this.oauthService.valiUserGroupAndAppAuthorize(this.getUserId(), this.getAppId())) { // 无权访问

				try {
					org.apache.shiro.web.util.WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
				} catch (IOException e) {
					e.printStackTrace();
					final HttpServletRequest request = oauthRequest.request();
					request.getRequestDispatcher("../unauthorized").forward(request, response);
					// WebUtils.writeOAuthQueryResponse(response,
					// oAuthResponse);
				}
				return true;
			}

		} catch (Exception ex) {
			// 错误时这里需要修改转发页面地址
			LOG.debug("vali authorize error", ex);
			try {
				org.apache.shiro.web.util.WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
			} catch (IOException e) {
				e.printStackTrace();
				final HttpServletRequest request = oauthRequest.request();
				request.getRequestDispatcher("../unauthorized").forward(request, response);
				// WebUtils.writeOAuthQueryResponse(response, oAuthResponse);
			}
			return true;
		}

		return false;
	}

	/**
	 * 验证是否已生成AccessToken
	 * 
	 * @author whm
	 * @return
	 * @throws ServletException
	 * @throws IOException
	 */
	protected boolean valiGranted() throws ServletException, IOException {
		try {
			final String clientId = clientId();
			final String username = (String) SecurityUtils.getSubject().getPrincipal();

			AccessToken accessToken = oauthService.loadAccessToken(clientId, username, null);

			if (accessToken != null) {
				if (accessToken.tokenExpired()) { // AccessToken已过期
					if (accessToken.refreshTokenExpired()) { // 刷新令牌已过期
						return false;
					} else { // 刷新令牌已过期
						accessToken = oauthService.changeAccessTokenByRefreshToken(accessToken.refreshToken(),
								accessToken.clientId());
					}
				}

				final OAuthResponse oAuthResponse = createTokenResponse(accessToken, true);
				LOG.debug("'token' response: {}", oAuthResponse);

				WebUtils.writeOAuthQueryResponse(response, oAuthResponse);

				return true;
			}
		} catch (Exception ex) {
			LOG.debug("vali granted error", ex);
			final HttpServletRequest request = oauthRequest.request();
			request.setAttribute("oauth_login_error", true);
			request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);
			return true;
		}

		return false;
	}

	protected void setAttribute() {
		Map<String, Object> userInfo = this.oauthService
				.findUserInfoByUserAccount((String) SecurityUtils.getSubject().getPrincipal());
		Map<String, Object> appInfo = this.oauthService.findAppInfo(clientId());

		this.setUserId((Integer) userInfo.get("USER_INFO_ID"));
		this.setAppId((Integer) appInfo.get("APP_INFO_ID"));
		this.setAppInfo(appInfo);
		this.setUserInfo(userInfo);
	}

	protected void logLogin(String userAccount, String isSuccess) {

		LoginLog loginLog = new LoginLog();

		int userInfoId = this.oauthService.findUserInfoIdByAccount(userAccount);
		String ipAdrress = WebUtils.retrieveClientIp(oauthRequest.request());
		String macAddress = "";
		/*
		 * try { macAddress = WebUtils.getMACAddress(ipAdrress); } catch
		 * (Exception e) { LOG.debug("无法获取MAC地址。"); e.printStackTrace(); }
		 */

		loginLog.setUserInfoId(userInfoId);
		loginLog.setUserAccount(userAccount);
		loginLog.setIsSuccess(isSuccess);
		loginLog.setIpAddress(ipAdrress);
		loginLog.setMacAddress(macAddress);

		this.oauthService.saveLoginLog(loginLog);
	}

	/**
	 * IP锁
	 * 
	 * @return
	 * @throws IOException
	 * @throws ServletException
	 */
	public boolean validIpLock() throws Exception {
		final HttpServletRequest request = oauthRequest.request();
		return RedisUtils.validIp(request, response);
	}

	/**
	 * 账号锁
	 * 
	 * @return
	 * @throws IOException
	 * @throws ServletException
	 */
	public boolean validAccountLock() throws Exception {

		final HttpServletRequest request = oauthRequest.request();

		String userAccount = request.getParameter(REQUEST_USERNAME);

		Map<String, Object> userInfoMap = this.oauthService.findUserInfoByUserAccount(userAccount);

		if (userInfoMap == null) {
			if(propertiesUtils.isCheckLock()) {
				// redis添加数据
				RedisUtils.addIpList(request);
			}

			request.setAttribute("username", request.getParameter(REQUEST_USERNAME));
			request.setAttribute("oauth_login_error", true);
			request.setAttribute("oauth_login_error_message", "用户名或密码错误！");
			request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);
			return true;
		}

		String lockLevel = (String) userInfoMap.get("LOCK_LEVEL");

		if ("4".equals(lockLevel)) {

			request.setAttribute("username", request.getParameter(REQUEST_USERNAME));
			request.setAttribute("oauth_login_error", true);
			request.setAttribute("oauth_login_error_message", "您的账号已被永久锁定，请联系系统管理员。");
			request.getRequestDispatcher(OAUTH_LOGIN_VIEW).forward(request, response);

			return true;
		}

		return RedisUtils.validAccount(request, response);

	}

	public void handle() throws Exception {

		// 认证IP锁
		if(propertiesUtils.isCheckLock()) {
			if (validIpLock()) {
				return;
			}
		}

		// validate
		if (validateFailed()) {
			return;
		}

		// Check need usr login
		if (goLogin()) {
			return;
		}

		// 是否需要登陆
		if (isSubmitLogin()) {
			if(propertiesUtils.isCheckLock()) {
				// 账号锁
				if (validAccountLock()) {
					return;
				}
			}
		}

		// submit login
		if (submitLogin()) {
			return;
		}

		// 验证是否为弱密码
		if(propertiesUtils.isCheckPasswd()) {
			if (isWeakPassword()) {
				return;
			}
		}

		// 获取并设置userId和appInfoId
		this.setAttribute();

		// vali authorize
		if (valiAuthorize()) {
			return;
		}

		// Check approval
		if (goApproval()) {
			return;
		}

		// Submit approval
		if (submitApproval()) {
			return;
		}

		// handle response
		handleResponse();
	}

	// Handle custom response content
	protected abstract void handleResponse() throws OAuthSystemException, IOException;

	public int getUserId() {
		return userId;
	}

	public void setUserId(int userId) {
		this.userId = userId;
	}

	public int getAppId() {
		return appId;
	}

	public void setAppId(int appId) {
		this.appId = appId;
	}

	public Map<String, Object> getAppInfo() {
		return appInfo;
	}

	public void setAppInfo(Map<String, Object> appInfo) {
		this.appInfo = appInfo;
	}

	public Map<String, Object> getUserInfo() {
		return userInfo;
	}

	public void setUserInfo(Map<String, Object> userInfo) {
		this.userInfo = userInfo;
	}

}
